﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PServiceBus.Core.Interface;
using PServiceBus.Core.Runtime;
using System.Xml;
using System.Xml.Linq;
using System.Reflection;
using System.Collections;
using PServiceBus.Core.Runtime.Transports;
using PServiceBus.Core.Runtime.Extensions;
using Phoenix.ReflectionExtensions;
using System.Collections.Concurrent;
using PServiceBus.Core.Runtime.Transformations;
using System.Threading.Tasks;
using PServiceBus.Core.Runtime.Attributes;
using System.Reflection.Emit;
using System.Text.RegularExpressions;

namespace PServiceBus.Core.Runtime.Transports {
    /// <summary>
    /// Provider class for managing everything to do with transport. such as filtering and data transformation for transport
    /// </summary>
    public static class TransportProvider {
        internal static readonly Regex _keyRegex = new Regex(@"\b(?<Left>[^\s]+)\s*(?<Operator>=|!|>|<|regex|like)\s*(?<Right>""[^""]+""|[^\s]+)",
            RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
        static ConcurrentDictionary<string, List<string>> _matches =
            new ConcurrentDictionary<string, List<string>>();

        /// <summary>
        /// Filters the given transport message based on subscriber defined message filter
        /// </summary>
        /// <param name="subscriber">The subscriber.</param>
        /// <param name="topicID">The topic ID.</param>
        /// <param name="message">The message.</param>
        /// <returns></returns>
        public static TransportMessage FilterMessage(ISubscriber subscriber, Guid topicID, TransportMessage message) {
            var tuple = subscriber.GetMessageFilter(topicID);
            var expression = tuple.Item1;
            var caseSensitive = tuple.Item2;
            if (String.IsNullOrWhiteSpace(expression)) return message;
            var cloneMessage = message.DeepClone();
            var parameters = new Dictionary<string, object>[message.Parameters.Count];
            var func = ExpressionGenerator.Build(expression, caseSensitive);
            var matches = _matches.GetOrAdd(expression, key =>
            {
                return _keyRegex.Matches(key).Cast<Match>()
                 .Select(x => x.Groups["Left"].Value.Replace("(", string.Empty))
                 .Where(x => !String.IsNullOrWhiteSpace(x))
                 .Select(x => x.Split('.')[0].Trim())
                 .Distinct().ToList();
            });
            var tasks = new List<Task>();
            var messageParameters = message.Parameters;
            var messageParametersCount = messageParameters.Count;
            for (var i = 0; i < messageParametersCount; i++) {
                tasks.Add(Task.Factory.StartNew(kv =>
                {
                    var keyValue = (KeyValuePair<int, Dictionary<string, object>>)kv;
                    var parameter = keyValue.Value.ToDictionary(x=> x.Key, x => x.Value);
                    var text = string.Empty;
                    var arguments = parameter.Where(x => matches.Any(m => m == x.Key))
                        .OrderBy(x => x.Key)
                        .Select(x => new { IsBase64 = ((text = x.Value as string) != null && text.IsXmlBase64()),
                            Value =  x.Value is TimeSpan || x.Value is Guid ? x.Value.ToString() : x.Value })
                        .Select(x => (x.Value is string) && !caseSensitive && !x.IsBase64 && x.Value != null 
                             ? x.Value.ToString().ToLower() : x.Value)
                        .ToArray();
                    if(func(arguments))
                        parameters[keyValue.Key] = parameter;
                }, new KeyValuePair<int, Dictionary<string, object>>(i, messageParameters[i])));
            }
            MethodHelper.TryLog(() => Task.WaitAll(tasks.ToArray()));
            cloneMessage.Parameters = parameters.ToList();
            return !parameters.IsEmpty() ? cloneMessage : null;
        }


        /// <summary>
        /// Convert data into the specific enumerable type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        /// <returns></returns>
        internal static IEnumerable<T> MessageTo<T>(string data) where T : class {
            var format = GetMessageFormat(data);
            var transformer = GetTransformation(format);
            UpdateHeaderProperty<T>(ref data);
            return transformer.Transform<T>(data);
        }

        private static IEnumerable<T> Transform<T>(ITransformation transformer, string data) where T : class {
            return transformer.Transform<T>(data);
        }

        private readonly static MethodInfo _transportProviderTransform = typeof(TransportProvider).GetMethod("Transform",
            BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);

        private readonly static ConcurrentDictionary<Type, Func<ITransformation, string, IEnumerable>>
            transformDelegates = new ConcurrentDictionary<Type, Func<ITransformation, string, IEnumerable>>();

        internal static IEnumerable<T> MessageObjectTo<T>(string data) where T : class {
            var format = GetMessageFormat(data);
            var transformer = GetTransformation(format);
            var messageType = transformer.GetCanonicalType(data);
            var func = transformDelegates.GetOrAdd(messageType, type => {
                var methodName = String.Concat("Transform", type.FullName.Replace(".", string.Empty));
                var dynamicMethod = new DynamicMethod(methodName, 
                    typeof(IEnumerable), new []{ typeof(ITransformation), typeof(string)}, true);
                var il = dynamicMethod.GetILGenerator();

                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Call, _transportProviderTransform.MakeGenericMethod(type));
                il.Emit(OpCodes.Ret);
                return dynamicMethod.CreateDelegate(typeof(Func<ITransformation, string, IEnumerable>))
                    as Func<ITransformation, string, IEnumerable>;
            });
            return func(transformer, data).Cast<T>();
        }

        /// <summary>
        /// Determine to format of the message based on content
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static TransportFormat GetMessageFormat(string data) {
            var format =
                data.StartsWith("<List") ? TransportFormat.Xml :
                    data.StartsWith("[") ? TransportFormat.Json :
                    data.Contains("-EOF-") ? TransportFormat.Text :
                    TransportFormat.Csv;
            return format;
        }

        private static void UpdateHeaderProperty<T>(ref string data) {
            var props = typeof(T).GetPropertiesEx();
            var topicHeaderProp = props.Where(prop => prop.GetCustomAttributes(typeof(TopicHeaderAttribute), true).Length > 0
                && prop.PropertyType == _dictType).FirstOrDefault();
            if (topicHeaderProp != null) data = data.Replace(Constants.ESBTOPIC_HEADERS, topicHeaderProp.Name);
        }

        private static readonly Type _dictType = typeof(Dictionary<string, string>);
        private static DependencyFinder<ITransport> _typeFinder = new DependencyFinder<ITransport>();

        private static string _messageTransportDir = null;
        /// <summary>
        /// Return the MessageTransport Directory for discovering ITransport dlls
        /// </summary>
        public static string MessageTransportDir {
            get {
                if (_messageTransportDir == null) {
                    var mainPath = AppDomain.CurrentDomain.BaseDirectory.Replace("\\", "/");
                    var dirs = new[] { "MessageTransports", "bin"};
                    var folders = dirs.Select(dir => String.Format("{0}/{1}/", mainPath, dir))
                            .Where(dir => System.IO.Directory.Exists(dir)).ToList();
                    folders.Add(".");//Add current directory
                    _messageTransportDir = String.Join(";", folders);
                }
                return _messageTransportDir;
            }
        }

        static ConcurrentDictionary<TransportFormat, ITransformation> _tranformations = new ConcurrentDictionary<TransportFormat, ITransformation>();

        /// <summary>
        /// Store transformations
        /// </summary>
        static TransportProvider() {
            _tranformations[TransportFormat.Json] = new JsonTransformation();
            _tranformations[TransportFormat.Text] = new TextTransformation();
            _tranformations[TransportFormat.Xml] = new XmlTransformation();
            _tranformations[TransportFormat.Csv] = new CsvTransformation();
        }

        /// <summary>
        /// Return the defined transformation for the specified format
        /// </summary>
        /// <param name="format"></param>
        /// <returns></returns>
        public static ITransformation GetTransformation(TransportFormat format) {
            return _tranformations[format];
        }
        
        /// <summary>
        /// Transform the message based on the given format
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="message">The message.</param>
        /// <returns></returns>
        public static string TransformMessage(TransportFormat format, TransportMessage message) {
            var transformer = GetTransformation(format);
            return transformer.Transform(message.Parameters);
        }
    }
}
